#! /bin/env python
"""Pretty print nested dada structures.

Exports a class PrettyPrinter and a function Print.
"""


from types import TupleType,ListType,DictType,InstanceType

recursiveTypes=(DictType,ListType,InstanceType)
listTypes=(ListType,TupleType)
class PrettyPrinter:
	def __init__(self,
				file      = None,
				indent    = None,
				separator = None,
				depth     = None,
				duplicate = None,
				show_id   = None,
				private   = None,
				cutlen    = None):
		if indent is None:
			indent='    '
		elif type(indent)==type(0):
			indent=indent*' '
		self.indent=indent
		if file is None:
			import sys
			file=sys.stdout
		self.file=file
		if separator is None:
			separator=''
		self.separator=separator
		self.depth=depth
		self.duplicate=duplicate
		self.show_id=show_id
		self.repr=self.makecutstr(cutlen)
		self.private=private
	def makecutstr(self,n):
		from regsub import gsub 
		def cutstr(value,n=n,gsub=gsub): # n for closure :-)
			v=repr(value)
			v=gsub("[\t \n]+"," ",v)
			if n and len(v)>n:
				v=v[:(n-2)/2]+"..."+v[-(n-2)/2:]
			return str(v)
		if n is None:
			return repr
		else:
			return cutstr
	def write(self,str):
		self.file.write(str)
	def nl(self,depth):
		self.write('\n'+ self.indent * depth)
	def GoDeeper(self,depth):
		"""Check is we are too deep in the recursion"""
		if self.depth is not None and depth>=self.depth:
			self.write('...')
			return 0
		return 1
		
	def Register(self,value):
		seen=0
		if type(value) in recursiveTypes:
			key=id(value)
			if self.seen.has_key(key):
				# don't print ourself again
				seen=1
			self.seen[key]=None
		return seen
	def Unregister(self,value):
		if type(value) in recursiveTypes:
			key=id(value)
			del self.seen[key]

	def Print(self,value):
		self.seen={} # helper instance to avoid infinite recursion
		self.VisitAny(value)
		self.nl(0)
		del self.seen
	def WriteList(self,name,value,depth,nl):
		separator=''
		self.write('%s (' % name)
		if self.GoDeeper(depth):
			for v in value:
				self.write(separator)
				self.VisitAny(v,depth+1,1)
				separator=self.separator
			if len(value):
				self.nl(depth)
		self.write(')')
	def WriteDict(self,name,dict,depth,nl,hide_prefix=''):
		if type(dict) is DictType:
			self.write(name)
			k=dict.keys()
			if k:
				l=max(map(len,map(str,k)))
			else:
				l=0
			format='%%-%ds' %l
			self.write(' (')
			separator=''
			strtype=type('')
			if self.GoDeeper(depth):
				try:
					keys=value.__print_order__()
				except:
					keys=dict.keys()
					keys.sort()
				if hide_prefix:
					l=len(hide_prefix)
					try:
						keys=filter(lambda x,l=l,p=hide_prefix:x[:l]!=p,keys)
					except:
						pass
				for k in keys:
					v=dict[k]
					self.write(separator)
					self.nl(depth+1)
					if type(k) == strtype:
						self.write(format % str(k))
					else:
						self.VisitAny(k,depth+1,0)
					self.write(" : ")
					self.VisitAny(v,depth+1,0)
					separator=self.separator
				if len(dict):
					self.nl(depth)
			self.write(')')
		else:
			self.write(' ')
			self.VisitAny(dict,depth,nl)
	def VisitList(self,value,depth=0,nl=0):
		self.WriteList('array',value,depth,nl)
	def VisitTuple(self,value,depth=0,nl=0):
		self.WriteList('tuple',value,depth,nl)
	def VisitDict(self,value,depth=0,nl=0):
		if value.has_key('__name__'):
			name=value['__name__']
			dict={}
			for k,v in value.items():
				if k!='__name__':
					dict[k]=v
			value=dict
		else:
			name='struct'
		self.WriteDict(name,value,depth,nl)
	def VisitInstance(self,value,depth=0,nl=0):
		try:
			getstate = value.__prettyprint__
		except:
			try:
				getstate = value.__getstate__
			except:
				state = value.__dict__
			else:
				state = getstate()
		else:
			state = getstate()
		# find the length of the longest name
		# build a format string lile "%11s"
		name=value.__class__.__name__
		if type(state) in listTypes:
			self.WriteList(name,state,depth,nl)
		else:
			# do we want to write private variables?
			if self.private:
				prefix=''
			else:
				prefix='_'+name+'__' # Private variables
			self.WriteDict(name,state,depth,nl,prefix)
	def VisitOther(self,value,depth=0,nl=0):
		self.write(self.repr(value))
	def VisitAny(self,value,depth=0,nl=0):
		if nl:
			self.nl(depth)
		if self.show_id:
			self.write("<%d>"%id(value))
		if self.Register(value):
			self.write('***RECURSION***')
			return
		t=type(value)
		if t is TupleType:
			self.VisitTuple(value,depth,nl)
		elif t is ListType:
			self.VisitList(value,depth,nl)
		elif t is DictType:
			self.VisitDict(value,depth,nl)
		elif t is InstanceType:
			self.VisitInstance(value,depth,nl)
		else:
			self.VisitOther(value,depth,nl)
		if self.duplicate:
			# we want to print this value again
			self.Unregister(value)

def Print(value,*args,**kws):
	"""Prints value to a file"""
	p=apply(PrettyPrinter,args,kws)
	p.Print(value)

def ToString(value,*args,**kws):
	import StringIO
	file=kws['file']=StringIO.StringIO()
	p=apply(PrettyPrinter,args,kws)
	try:
		p.Print(value)
	except:
		pass
	return file.getvalue()
	

def _test():
	class Person:
		def __init__(self,**attrs):
			for name,val in attrs.items():
				setattr(self,name,val)
	
	guido=Person(
		Name='Guido',
		SirName='van Rossum',
		Language='python',	
		Keywords=('for','class','and','def')
	)
	larry=Person(
		Name='Larry', 
		SirName='Wall',
		Language='perl',
		Keywords=['&','$','@','%','/']
	)
	# let's create a recursive data structure
	guido.Competitor=larry
	larry.Competitor=guido
	# and a list with two guidos
	data=(guido,larry,guido,{'dict':{},'list':[1,2]})
	Print(data,duplicate=1)
	Print(data,indent='  ',separator=',',depth=3)
	guido.Competitor=None # remove larry to stop the recussion ;-)
	# how does a pretty printer look like??
	pp=PrettyPrinter(indent=' ',separator=';')
	pp.Print(pp)
		
if __name__=='__main__':
	_test()
